/**
 * 
 */
/*!
 * Ajax v1.2.0
 * (c) 2019-12-29 
 * @author 戡玉
 * Released under the MIT License.
 */
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global.Ajax = factory());
}(this, function () {
    'use strict';
    var toString = Object.prototype.toString,
        urlParsingNode = document.createElement('a'),
        currentOrigin = resolveURL(window.location.href);

    // Date.prototype.toISOString垫片代码(ie8不支持)
    Date.prototype.toISOString = Date.prototype.toISOString || function () {
        function pad(number) {
            if (number < 10) {
                return '0' + number;
            }
            return number;
        }
        return this.getUTCFullYear() +
            '-' + pad(this.getUTCMonth() + 1) +
            '-' + pad(this.getUTCDate()) +
            'T' + pad(this.getUTCHours()) +
            ':' + pad(this.getUTCMinutes()) +
            ':' + pad(this.getUTCSeconds()) +
            '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
            'Z';
    };

    // Array.prototype.indexOf垫片代码(ie8不支持)
    Array.prototype.indexOf = Array.prototype.indexOf || function (value) {
        return this.toString().indexOf(value) - (this.toString().length - 1) / 2;
    }

    /**
     * 判断是否为日期对象
     * @param {any} val 要判断的对象
     */
    function isDate(val) {
        return toString.call(val) === '[object Date]';
    }

    /**
     * 判断是否为JS对象
     * @param {any} val 要判断的对象
     */
    function isPlainObject(val) {
        return toString.call(val) === '[object Object]'
    }

    /**
     * 判断是否为非空对象
     * @param {any} val 要判断的对象
     */
    function isObject(val) {
        return val !== null && typeof val === 'object';
    }

    /**
     * 判断是否为数组
     * @param {any} val 要判断的对象
     */
    function isArray(val) {
        return toString.call(val) === '[object Array]';
    }

    /**
     * 判断是否为函数
     * @param {any} val 要判断的对象
     */
    function isFunction(val) {
        return toString.call(val) === '[object Function]';
    }

    /**
     * 判断是否为表单对象
     * @param {any} val 要判断的对象
     */
    function isFormData(val) {
        return typeof val !== 'undefined' && typeof FormData !== 'undefined' && val instanceof FormData;
    }

    /**
     * ForEach 封装方法 支持对象和数组
     * @param {Objcet|Array} obj 要遍历的对象或者数组
     * @param {Function} fn 遍历回调函数
     */
    function forEach(obj, fn) {
        if (obj === null || typeof obj === 'undefined') {
            return;
        }

        var _isArray = isArray(obj);

        if (typeof obj !== 'object' && !_isArray) {
            obj = [obj];
        }

        if (_isArray) {
            for (var i = 0, l = obj.length; i < l; i++) {
                if (fn.call(null, obj[i], i, obj) === false) return false;
            }
        } else {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    if (fn.call(null, obj[key], key, obj) === false) return false;
                }
            }
        }
    }

    /**
     * 合并配置对象
     * @param {Object} join 要合并的对象象
     * @param {Object} base 基础对象（被合并的对象）
     */
    function mergeConfig(join, base) {
        var base = base || {};
        for (var key in base) {
            if (typeof join[key] === 'undefined') {
                if (typeof base[key] === 'object') {
                    join[key] = (base[key].constructor === Array) ? [] : {};
                    mergeConfig(join[key], base[key]);
                } else {
                    join[key] = base[key];
                }
            }
        }
        return join;
    }

    /**
     * 去除字符串两端空格
     * @param {String} str 要处理的字符串
     */
    function trim(str) {
        return str.replace(/^\s*/, '').replace(/\s*$/, '');
    }

    /**
     * 将指定的特殊字符转码成URI编码
     * 包含的字符：
     * @
     * :
     * $
     * ,
     * +
     * [
     * ]
     * @param {String} val 要处理的字符串
     */
    function encode(val) {
        return encodeURIComponent(val)
            .replace(/%40/g, '@')
            .replace(/%3A/gi, ':')
            .replace(/%24/g, '$')
            .replace(/%2C/gi, ',')
            .replace(/%20/g, '+')
            .replace(/%5B/gi, '[')
            .replace(/%5D/gi, ']')
    }

    /**
     * 读取指定键名的Cookie
     * @param {String} name cookie 键名
     */
    function readCookie(name) {
        var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'))
        return match ? decodeURIComponent(match[3]) : null
    }

    /**
     * 从URL字符串中提取
     * @param {String} url 要处理的URL字符串
     */
    function resolveURL(url) {
        urlParsingNode.setAttribute('href', url);
        return {
            protocol: urlParsingNode.protocol,
            host: urlParsingNode.host
        }
    }

    /**
     * 判断目标URL是否与当前的URL同源（是否跨域）
     * @param {String} requestURL 要判断的URL字符串
     */
    function isURLSameOrigin(requestURL) {
        var parsedOrigin = resolveURL(requestURL);
        return parsedOrigin.protocol === currentOrigin.protocol && parsedOrigin.host === currentOrigin.host;
    }

    /**
     * 判断是否为绝对路径
     * @param {String} url 要判断的URL
     */
    function isAbsoluteURL(url) {
        return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
    }

    /**
     * 将相对URL与指定的基础URL合并成绝对路径
     * @param {String} baseURL 基本URL 例如：http://www.baidu.com
     * @param {String} relativeURL 要合并的相对URL路径
     */
    function combineURL(baseURL, relativeURL) {
        return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
    }

    // 去掉hash得到干净得url 处理params对象的参数对象，接到Url后面

    function transformURL(config) {
        var baseURL = config.baseURL,
            url = config.url,
            params = config.params;

        if (baseURL && !isAbsoluteURL(url)) {
            url = combineURL(baseURL, url);
        }

        if (!params) {
            return url;
        }

        var parts = [];

        forEach(params, function (val, key) {
            if (val === null || typeof val === 'undefined') {
                return;
            }
            if (!isArray(val)) {
                val = [val];
            }

            forEach(val, function (v) {
                if (isDate(v)) {
                    v = v.toISOString();
                }
                else if (isObject(v)) {
                    v = JSON.stringify(v);
                }
                parts.push(encode(key) + '=' + encode(v));
            });
        });

        var markIndex = url.indexOf('#');
        if (markIndex !== -1) {
            url = url.slice(0, markIndex)
        }

        if (parts.length > 0) {
            url += (url.indexOf('?') === -1 ? '?' : '&') + parts.join('&');
        }

        return url;
    }

    // 转换headers
    function transformHeaders(config) {
        var headers = config.headers,
            data = config.data,
            normalizedName = 'Content-Type';

        if (data !== null && data !== undefined) {
            if (headers) {
                for (var name in headers) {
                    if (headers.hasOwnProperty(name)) {
                        if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
                            headers[normalizedName] = headers[name]
                            delete headers[name]
                        }
                    }
                }
            }
            if (isPlainObject(data)) {
                if (!headers) {
                    headers = {
                        'Content-Type': 'application/json;charset=utf-8'
                    }
                } else if (!headers['Content-Type']) {
                    headers['Content-Type'] = 'application/json;charset=utf-8';
                }
            }
        } else {
            for (var name in headers) {
                if (headers.hasOwnProperty(name)) {
                    if (name.toLowerCase() === 'content-type') {
                        delete headers[name]
                    }
                }
            }
        }

        return headers || {};
    }

    // 转换data
    function transformData(config) {
        var data = config.data;
        if (isPlainObject(data)) {
            return JSON.stringify(data)
        }
        return data
    }

    // 解析header
    function parseHeaders(headers) {
        var parsed = {}, key, val, i;

        if (!headers) return parsed;

        forEach(headers.split('\n'), function (line) {
            i = line.indexOf(':');
            key = trim(line.substr(0, i)).toLowerCase();
            val = trim(line.substr(i + 1));

            if (key) {
                parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
            }
        });

        return parsed;
    };

    // 解析data
    function parseData(data) {
        if (typeof data === 'string') {
            try {
                return JSON.parse(data);
            } catch (e) {
                // do nothing
            }
        }
        return data
    }

    // 处理config
    function processConfig(config) {
        config.url = transformURL(config);
        config.headers = transformHeaders(config);
        config.data = transformData(config);
        return hookIntercept(config, config.before);
    }

    // 处理Response
    function processResponse(response) {
        var config = response.config,
            request = response.request,
            errorInfo;
        if (response.status >= 200 && response.status < 300) {
            response.data = parseData(response.data);
            hookIntercept(response, config.after);
        } else {
            errorInfo = {
                message: 'Request failed with status code ' + response.status,
                code: null,
                config: config,
                request: request,
                response: response,
            }
            hookIntercept(errorInfo, config.error);
        }
    }

    // 钩子处理
    function hookIntercept(result, callback) {
        if (callback && typeof callback === 'function') {
            return callback(result);
        }
    }

    // 发送请求
    function dispatchRequest(config) {
        var url = config.url,
            data = config.data,
            method = config.method,
            headers = config.headers,
            timeout = config.timeout,
            responseType = config.responseType,
            xsrfCookieName = config.xsrfCookieName,
            xsrfHeaderName = config.xsrfHeaderName,
            withCredentials = config.withCredentials,
            onDownloadProgress = config.onDownloadProgress,
            onUploadProgress = config.onUploadProgress;

        var request = new XMLHttpRequest();

        request.open(method.toUpperCase(), url, true);

        // 设置响应数据类型
        if (responseType) {
            try {
                request.responseType = responseType;
            } catch (e) {
                if (request.responseType !== 'json') {
                    throw e;
                }
            }
        }

        // 设置xsrf
        if ((withCredentials || isURLSameOrigin(url)) && xsrfCookieName) {
            var xsrfValue = readCookie(xsrfCookieName)
            if (xsrfValue) {
                headers[xsrfHeaderName] = xsrfValue
            }
        }

        // 跨域请求时，是否带上cookie凭据
        if (withCredentials) {
            request.withCredentials = true
        }

        // 设置超时时间
        if (timeout) {
            request.timeout = timeout;
        }

        // 设置下载进度钩子
        if (onDownloadProgress) {
            request.onprogress = onDownloadProgress
        }

        // 设置上传进度钩子
        if (onUploadProgress) {
            request.upload.onprogress = onUploadProgress
        }

        // 如果是FormData类型，删除默认Content-Type，让游览器自动添加
        if (isFormData(data)) {
            delete headers['Content-Type']
        }

        // 设置头部
        for (var name in headers) {
            if (headers.hasOwnProperty(name)) {
                request.setRequestHeader(name, headers[name])
            }
        }

        // 监听xhr状态
        request.onreadystatechange = function () {
            // 非成功状态，不处理逻辑
            if (request.readyState !== 4) {
                return
            }
            // 当出现网络错误或者超时错误的时候，该值都为 0
            if (request.status === 0) {
                return
            }
            // 组合响应数据
            var responseHeaders = parseHeaders(request.getAllResponseHeaders());
            var responseData = responseType && responseType !== 'text' ? request.response : request.responseText;
            var response = {
                data: responseData,
                status: request.status,
                statusText: request.statusText,
                headers: responseHeaders,
                config: config,
                request: request
            }
            processResponse(response);
        }

        // 监听取消
        request.onabort = function handleAbort() {
            if (!request) return;

            hookIntercept({
                message: 'Request aborted',
                code: 'ECONNABORTED',
                config: config,
                request: request
            }, config.error)
        };

        // 监听超时
        request.ontimeout = function () {
            hookIntercept({
                message: 'Timeout of ' + config.timeout + ' ms exceeded',
                code: 'ECONNABORTED',
                config: config,
                request: request
            }, config.error)
        }

        // 监听网络异常错误
        request.onerror = function () {
            hookIntercept({
                message: 'Network Error',
                code: null,
                config: config,
                request: request
            }, config.error)
        }

        request.send(data)

        return request;
    }

    // Ajax对象
    function Ajax(config) {
        if (processConfig(config) !== false) {
            return dispatchRequest(config);
        } else {
            return {
                abort: function () { }
            }
        }
    }

    // 创建HTTP方法
    function createHttpMethod(Http) {
        function parseArguments(method, url, data, success, config) {
            if (isFunction(data)) {
                config = success;
                success = data;
                data = undefined;
            }
            return mergeConfig({
                url: url,
                method: method,
                params: ['get', 'delete', 'head', 'options'].indexOf(method) >= 0 ? data : undefined,
                data: ['post', 'put', 'patch'].indexOf(method) >= 0 ? data : undefined,
                after: success,
            }, config);
        }
        forEach(['get', 'delete', 'head', 'options', 'post', 'put', 'patch'], function (method) {
            Http[method] = function (url, data, success, config) {
                return Http(parseArguments(method, url, data, success, config));
            }
        })
        return Http;
    }

    // 创建Ajax实例
    Ajax.create = function (baseConfig) {
        return createHttpMethod(function (joinConfig) {
            return Ajax(mergeConfig(joinConfig, baseConfig));
        });
    }

    return createHttpMethod(Ajax);
}));